// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
#pragma once

#include <memory>
#include <string>
#include <vector>
#include <pollux/functions/lib/date_time_formatter.h>

namespace kumo::pollux::functions {
    class DateTimeFormatter;
    enum class DateTimeFormatterType;
    struct DateTimeToken;

    /// Builder class that builds DateTimeFormatter object. For example, let's
    /// assume MySQL date time standard is used. To build a formatter of the form
    /// '%Y-%m-%d' you do:
    /// DateTimeFormatterBuilder(8)     // Length of format
    ///     .appendYear(4)              // Year of 4 digits
    ///     .appendLiteral("-")
    ///     .appendMonthOfYear(2)       // Month of 2 digits
    ///     .appendLiteral("-")
    ///     .appendDayOfMonth(2)        // Day of 2 digits
    ///     .build();
    class DateTimeFormatterBuilder {
    public:
        explicit DateTimeFormatterBuilder(size_t literalBufSize);

        /// Appends era to formatter builder, e.g: "AD"
        DateTimeFormatterBuilder &appendEra();

        /// Appends century of era (>=0) to formatter builder, e.g: 20
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent century of era. The format by default is going use
        /// as few digits as possible but greater than or equal to minDigits to
        /// represent century of era. e.g. year 999, with min digit being 1 the
        /// formatted result will be 9 , with min digit being 2 the formatted result
        /// will be 09
        DateTimeFormatterBuilder &appendCenturyOfEra(size_t minDigits);

        /// Appends year of era (>=0) to formatter builder, e.g: 1999
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent year of era. The format by default is going use as
        /// few digits as possible greater than or equal to minDigits to represent
        /// year of era. e.g. year 1999, with min digit being 1 the formatted result
        /// will be 99 (year of century), with min digit being 2 the formatted result
        /// will be 99 (year of century), with min digit being 3 the formatted result
        /// will be 1999, with min digit being 4 the formatted result will be 1999,
        /// and with min digit being 5 the formatted result will be 01999
        DateTimeFormatterBuilder &appendYearOfEra(size_t minDigits);

        /// Appends week year to formatter builder, e.g: 1999
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent week year. The format by default is going use as few
        /// digits as possible greater than or equal to minDigits to represent
        /// week year. e.g. 1999-01-01, with min digit being 1 the formatted result
        /// will be 98 (year of century), with min digit being 2 the formatted result
        /// will be 98 (year of century), with min digit being 3 the formatted result
        /// will be 1998, with min digit being 4 the formatted result will be 1998,
        /// and with min digit being 5 the formatted result will be 01998
        DateTimeFormatterBuilder &appendWeekYear(size_t minDigits);

        /// Appends week of week year to formatter builder, e.g: 2
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent week of week year. The format by default is going
        /// use as few digits as possible greater than or equal to minDigits to
        /// represent week of week year. e.g. 1999-01-08, with min digit being 1 the
        /// formatted result will be 1, with min digit being 2 the formatted result
        /// will be 01 (year of century), with min digit being 3 the formatted result
        /// will be 001
        DateTimeFormatterBuilder &appendWeekOfWeekYear(size_t minDigits);

        /// Appends week of month to formatter builder, e.g: 2
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent week of month. The format by default is going
        /// use as few digits as possible greater than or equal to minDigits to
        /// represent week of month. e.g. 1999-01-01, with min digit being 1 the
        /// formatted result will be 1, with min digit being 4 the formatted result
        /// will be 0001
        DateTimeFormatterBuilder &appendWeekOfMonth(size_t minDigits);

        /// Appends day of week to formatter builder. The number is 0 based with 0 ~ 6
        /// representing Sunday to Saturday respectively
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent day of week. The format by default is going use as
        /// few digits as possible greater than or equal to minDigits to represent day
        /// of week. e.g. 1999-01-01, with min digit being 1 the formatted result will
        /// be 5, with min digit being 2 the formatted result will be 05
        DateTimeFormatterBuilder &appendDayOfWeek0Based(size_t minDigits);

        /// Appends day of week to formatter builder. The number is 1 based with 1 ~ 7
        /// representing Monday to Sunday respectively
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent day of week. The format by default is going use as
        /// few digits as possible greater than or equal to minDigits to represent day
        /// of week. e.g. 1999-01-01, with min digit being 1 the formatted result will
        /// be 5, with min digit being 2 the formatted result will be 05
        DateTimeFormatterBuilder &appendDayOfWeek1Based(size_t minDigits);

        /// Appends day of week text to formatter builder, e.g: 'Tue' or 'Tuesday'
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent day of week in text. The format by default is going
        /// use as few digits as possible greater than or equal to minDigits to
        /// represent day of week. e.g. 1999-01-01, with min digit being 1 the
        /// formatted result will be 'Fri', with min digit being 4 the formatted
        /// result will be 'Friday'
        DateTimeFormatterBuilder &appendDayOfWeekText(size_t minDigits);

        /// Appends year to formatter builder, e.g: 1999
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent year. The format by default is going use as
        /// few digits as possible greater than or equal to minDigits to represent
        /// year. e.g. year 1999, with min digit being 1 the formatted result will be
        /// 99 (year of century), with min digit being 2 the formatted result will be
        /// 99 (year of century), with min digit being 3 the formatted result will be
        /// 1999, with min digit being 4 the formatted result will be 1999, and with
        /// min digit being 5 the formatted result will be 01999
        DateTimeFormatterBuilder &appendYear(size_t minDigits);

        /// Appends day of year to formatter builder, e.g: 365
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent day of year. The format by default is going use as
        /// few digits as possible greater than or equal to minDigits to represent day
        /// of year. e.g. 1999-01-01, with min digit being 1 the formatted result will
        /// be 1, with min digit being 4 the formatted result will be 0001
        DateTimeFormatterBuilder &appendDayOfYear(size_t minDigits);

        /// Appends month of year to formatter builder, e.g: 12
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent month of year. The format by default is going use as
        /// few digits as possible greater than or equal to minDigits to represent
        /// month of year. e.g. 1999-01-01, with min digit being 1 the formatted
        /// result will be 1, with min digit being 4 the formatted result will be 0001
        DateTimeFormatterBuilder &appendMonthOfYear(size_t minDigits);

        /// Appends month of year in text to formatter builder, e.g: Jan, Feb
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent month of year in text. The format by default is
        /// going use as few digits as possible greater than or equal to minDigits to
        /// represent month of year. e.g. 1999-01-01, with min digit being 1 the
        /// formatted result will be Jan, with min digit being 4 the formatted result
        /// will be January
        DateTimeFormatterBuilder &appendMonthOfYearText(size_t minDigits);

        /// Appends day of month to formatter builder, e.g: 30
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent day of month. The format by default is going use as
        /// few digits as possible greater than or equal to minDigits to represent day
        /// of month. e.g. 1999-01-01, with min digit being 1 the formatted result
        /// will be 1, with min digit being 4 the formatted result will be 0001
        DateTimeFormatterBuilder &appendDayOfMonth(size_t minDigits);

        /// Appends half day of day to formatter builder, e.g: 'AM' or 'PM'
        DateTimeFormatterBuilder &appendHalfDayOfDay();

        /// Appends hour of half day to formatter builder, e.g: 11
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent hour of half day. The format by default is going use
        /// as few digits as possible greater than or equal to minDigits to represent
        /// hour of half day. e.g. 1999-01-01 01:00:00, with min digit being 1 the
        /// formatted result will be 1, with min digit being 4 the formatted result
        /// will be 0001
        DateTimeFormatterBuilder &appendHourOfHalfDay(size_t minDigits);

        /// Appends clock hour of half day to formatter builder, e.g: 12
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent clock hour of half day. The format by default is
        /// going use as few digits as possible greater than or equal to minDigits to
        /// represent clock hour of half day. e.g. 1999-01-01 00:00:00, with min digit
        /// being 1 the formatted result will be 12, with min digit being 4 the
        /// formatted result will be 0012
        DateTimeFormatterBuilder &appendClockHourOfHalfDay(size_t minDigits);

        /// Appends hour of day to formatter builder, e.g: 23
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent hour of day. The format by default is going use
        /// as few digits as possible greater than or equal to minDigits to represent
        /// hour of day. e.g. 1999-01-01 01:00:00, with min digit being 1 the
        /// formatted result will be 1, with min digit being 4 the formatted result
        /// will be 0001
        DateTimeFormatterBuilder &appendHourOfDay(size_t minDigits);

        /// Appends clock hour of day to formatter builder, e.g: 24
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent clock hour of day. The format by default is going
        /// use as few digits as possible greater than or equal to minDigits to
        /// represent clock hour of day. e.g. 1999-01-01 01:00:00, with min digit
        /// being 1 the formatted result will be 1, with min digit being 4 the
        /// formatted result will be 0001
        DateTimeFormatterBuilder &appendClockHourOfDay(size_t minDigits);

        /// Appends minute of hour to formatter builder, e.g: 59
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent minute of hour. The format by default is going
        /// use as few digits as possible greater than or equal to minDigits to
        /// represent minute of hour. e.g. 1999-01-01 01:59:00, with min digit
        /// being 1 the formatted result will be 59, with min digit being 4 the
        /// formatted result will be 0059
        DateTimeFormatterBuilder &appendMinuteOfHour(size_t minDigits);

        /// Appends second of minute to formatter builder, e.g: 59
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent second of minute. The format by default is going
        /// use as few digits as possible greater than or equal to minDigits to
        /// represent second of minute. e.g. 1999-01-01 01:59:00, with min digit
        /// being 1 the formatted result will be 59, with min digit being 4 the
        /// formatted result will be 0059
        DateTimeFormatterBuilder &appendSecondOfMinute(size_t minDigits);

        /// Appends fraction of second to formatter builder, e.g: 998
        ///
        /// \param digits describes the number of digits this format uses to represent
        /// fraction of second. e.g. 1999-01-01 01:59:00.987, with digit being 1 the
        /// formatted result will be 9, with digit being 4 the formatted result will
        /// be 9870, with digit being 6 the formatted result will be 987000
        DateTimeFormatterBuilder &appendFractionOfSecond(size_t digits);

        /// Appends time zone to formatter builder, e.g: 'Pacific Standard Time' or
        /// 'PST'
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent time zone. The format by default is going
        /// use as few digits as possible greater than or equal to minDigits to
        /// represent time zone.
        DateTimeFormatterBuilder &appendTimeZone(size_t minDigits);

        /// Appends time zone offset id to formatter builder, e.g: '-0800' or '-08:00'
        /// or 'America/Los_Angeles'
        ///
        /// \param minDigits describes the minimum number of digits this format is
        /// required to represent time zone offset id. The format by default is going
        /// use as few digits as possible greater than or equal to minDigits to
        /// represent time zone offset id.
        DateTimeFormatterBuilder &appendTimeZoneOffsetId(size_t minDigits);

        DateTimeFormatterBuilder &appendLiteral(const std::string_view &literal);

        DateTimeFormatterBuilder &appendLiteral(
            const char *literalStart,
            size_t literalSize);

        DateTimeFormatterBuilder &set_type(DateTimeFormatterType type);

        std::shared_ptr<DateTimeFormatter> build();

    private:
        std::unique_ptr<char[]> literalBuf_;
        size_t bufEnd_{0};
        std::vector<DateTimeToken> tokens_;
        DateTimeFormatterType type_{DateTimeFormatterType::UNKNOWN};
    };
} // namespace kumo::pollux::functions
